<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script>
    {
        /**
         * 为函数绑定特定的this对象和预设参数，并返回一个新函数
         * @param {Object} thisArg - 目标函数执行时的this绑定对象
         * @param {...*} bindArgs - 需要预先传递给目标函数的参数序列
         * @returns {Function} - 包装后的绑定函数，具有以下特性：
         *  1. 普通调用时使用绑定的thisArg上下文
         *  2. new调用时忽略thisArg并创建新实例
         *  3. 自动合并预设参数和调用时传入的参数
         */
        Function.prototype.myBind = function (thisArg, ...bindArgs) {
            // 保存原始函数引用
            const func = this

            return function (...args) {
                // 合并预设参数和实际调用参数
                const allArgs = [...bindArgs, ...args]

                // 判断是否通过new操作符调用
                if (new.target) {
                    // 构造函数调用：忽略绑定的this，使用新创建的对象
                    return new func(...allArgs)
                } else {
                    // 普通函数调用：应用绑定的this上下文
                    return func.apply(thisArg, allArgs)
                }
            }
        }

        {
            function sum(a, b) {
                return this.base + a + b;
            }
            const obj = {base: 1};

            // 绑定 this 为 obj，预设参数 a = 2
            const boundSum = sum.myBind(obj, 2);

            console.log(boundSum(3)); // 输出：6（1 + 2 + 3）
            function Person(name, age) {
                this.name = name;
                this.age = age;
            }
        }

        {
            // 绑定第一个参数 name = 'John'
            const PersonFactory = Person.myBind(null, 'John');

            // 通过 new 调用绑定后的函数
            const p = new PersonFactory(30);

            console.log(p.name); // 'John'
            console.log(p.age);  // 30
        }

        {
            function logThisAndArgs() {
                console.log('this:', this);
                console.log('arguments:', arguments);
            }

            const obj = {name: 'obj'};

            // 绑定 this 为 obj，预设参数 'arg1'
            const boundLog = logThisAndArgs.myBind(obj, 'arg1');

            // 调用时传入 'arg2'
            boundLog('arg2');

            // 输出：
            // this: { name: 'obj' }
            // arguments: [ 'arg1', 'arg2' ]
        }
    }
    {
        // 手写call
        Function.prototype.myCall = function (thisArg,...argArray){
            const globalThis = typeof window !== undefined ? window : typeof global !== undefined ? global : self
            thisArg = thisArg  !== undefined  && thisArg !== null ?  thisArg : globalThis
            const key  = Symbol('tempFuncKey')
            thisArg[key] = this
            const result = thisArg[key](...argArray)
            delete thisArg[key]
            return result
        }
        function greet(greeting, punctuation) {
            console.log(`${greeting}, ${this.name}${punctuation}`);
        }

        const obj = { name: "Alice" };

        greet.myCall(obj, "Hello", "!");
        // 预期输出：Hello, Alice!
    }

    {
        /**
         * 在函数原型上实现自定义的apply方法，用于改变函数执行时的this指向
         * @param {Object} thisArg - 函数运行时绑定的this对象。若为null/undefined则使用全局对象
         * @param {Array} [argArray] - 传递给函数的参数数组（可选）
         * @returns {*} - 函数的执行结果
         */
        Function.prototype.myApply = function(thisArg,argArray){
            // 确定全局对象：浏览器环境使用window，Node环境使用global，Web Worker环境使用self
            const globalThis = typeof window !== undefined ? window : typeof global !== undefined ? global : self

            // 处理thisArg：当传入null/undefined时默认绑定全局对象
            thisArg = thisArg !== null && thisArg !== undefined ? thisArg : globalThis

            // 创建唯一Symbol作为临时属性键，避免属性冲突
            const key = Symbol('tempFuncKey')

            // 将当前函数绑定到目标对象的临时属性上
            thisArg[key] = this

            // 执行函数并展开参数数组（若未传argArray则相当于无参调用）
            const result = thisArg[key](...argArray)

            // 删除临时绑定属性，避免污染目标对象
            delete thisArg[key]

            return result
        }
        function greet(greeting, punctuation) {
            console.log(`${greeting}, ${this.name}${punctuation}`);
        }

        const obj = { name: "Alice" };

        greet.myApply(obj, ["Hello", "!"]);
        // 预期输出：Hello, Alice!
    }
</script>
</html>
